package base.transporte;

import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import base.GerenciadorConexoes;
import base.LinkedBlockingQueue;

public class SRWelcomer implements Runnable {
	private ArrayList<Integer> portasDisponiveis;
	private HashMap<SocketAddress, SRSocket> socketsAbertos;
	private BlockingQueue<SRSocket> filaSockets;
	private UDPSocketSR socketUDP;
	private String nome;
	private Thread this_thread;
	private AtomicBoolean listening;

	public SRWelcomer(int port, String nome) throws SocketException {
		this.nome = nome;
		socketUDP = new UDPSocketSR(port);
		filaSockets = new LinkedBlockingQueue<SRSocket>();

		portasDisponiveis = new ArrayList<Integer>();
		for (int i = 48000; i < 50000; i++) {
			portasDisponiveis.add(i);
		}
		socketsAbertos = new HashMap<SocketAddress, SRSocket>();

		listening = new AtomicBoolean(true);
		this_thread = new Thread(this);
		this_thread.start();

		GerenciadorConexoes.getInstance().inserirSRWelcomer(this);
	}

	public void desativar() {
		listening.set(false);
		this_thread.interrupt();
		GerenciadorConexoes.getInstance().removerSRWelcomer(this);
	}

	public SRSocket accept() {
		try {
			return this.filaSockets.take();
		} catch (InterruptedException e) {
			return null;
		}
	}

	private int abrirSocket(InetAddress ip, int port, SocketAddress ip_port) {
		boolean abriuSocket = false;
		int porta = 0;

		while (!abriuSocket) {
			// porta = portasDisponiveis.remove(0);
			try {
				SRSocket socket = new SRSocket(ip, port, nome);
				porta = socket.getLocalPort();
				socketsAbertos.put(ip_port, socket);
				try {
					filaSockets.put(socket);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				abriuSocket = true;
			} catch (SocketException e) {
				System.out.println("Erro ao abrir a porta " + porta);
			}
		}

		return porta;
	}

	private void fecharSocket(SocketAddress ip_port) {
		SRSocket socket = socketsAbertos.remove(ip_port);
		int porta = socket.getLocalPort();
		socket.desativar();
		portasDisponiveis.add(porta);
	}

	@Override
	public void run() {
		while (listening.get() == true) {
			SRPacket rcvPkt = null;
			try {
				rcvPkt = socketUDP.receivePacket();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (listening.get() == true) {
				String cmd = new String(rcvPkt.getData());
				InetAddress client_ip = rcvPkt.getSenderIp();
				int client_port = rcvPkt.getSenderPort();
				SocketAddress client_ip_port = rcvPkt.getSocketAddress();

				StringTokenizer tokenizedLine = new StringTokenizer(cmd);
				String firstToken = tokenizedLine.nextToken();
				if (firstToken.equals("ABRIR")) {
					String secondToken = tokenizedLine.nextToken();
					int portaNoCliente = Integer.parseInt(secondToken);
					int porta_criada = 0;
					if (socketsAbertos.containsKey(client_ip_port)) {
						porta_criada = socketsAbertos.get(client_ip_port).getLocalPort();
					} else {
						porta_criada = this.abrirSocket(client_ip, portaNoCliente, client_ip_port);
					}
					String resposta = "ABERTO " + porta_criada;
					try {
						SRPacket sndPkt = new SRPacket(0, resposta.getBytes());
						socketUDP.sendPacket(sndPkt, client_ip, client_port);
					} catch (IOException e) {
						e.printStackTrace();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				} else if (firstToken.equals("FECHAR")) {
					if (socketsAbertos.containsKey(client_ip_port))
						this.fecharSocket(client_ip_port);

					System.out.println("Socket " + client_ip_port + " fechado, qtd de skts abertos: " + socketsAbertos.size());
					String resposta = "FECHADO";
					try {
						SRPacket sndPkt = new SRPacket(0, resposta.getBytes());
						socketUDP.sendPacket(sndPkt, client_ip, client_port);
					} catch (IOException e) {
						e.printStackTrace();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}

	}
}
